package com.ruoyi.system.service.complex;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.system.bean.MapRouteInfo;
import com.ruoyi.system.bean.PageData;
import com.ruoyi.system.bean.ResultDTO;
import com.ruoyi.system.bean.req.QueryTravelInfoReq;
import com.ruoyi.system.bean.req.TravelInfoPoiReq;
import com.ruoyi.system.bean.req.TravelInfoReq;
import com.ruoyi.system.bean.rsp.PoiRsp;
import com.ruoyi.system.bean.rsp.TravelInfoPoiRsp;
import com.ruoyi.system.bean.rsp.TravelInfoRsp;
import com.ruoyi.system.domain.Poi;
import com.ruoyi.system.domain.TravelInfo;
import com.ruoyi.system.domain.TravelInfoPoi;
import com.ruoyi.system.service.IPoiService;
import com.ruoyi.system.service.ITravelInfoPoiService;
import com.ruoyi.system.service.ITravelInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.stream.Collectors;


/**
 * PoiComplexService类用于提供与poi（兴趣点）相关的复杂查询服务
 * 它集成了IPoiService、ITravelInfoService和ITravelInfoPoiService的功能
 * 来提供与旅行信息和poi数据相关的综合查询服务
 */
@Slf4j
@Service
public class PoiComplexService {

    // IPoiService用于操作poi数据的接口实例
    @Resource
    private IPoiService poiService;
    // ITravelInfoService用于操作旅行信息数据的接口实例
    @Resource
    private ITravelInfoService travelInfoService;
    // ITravelInfoPoiService用于操作旅行信息和poi关联数据的接口实例
    @Resource
    private ITravelInfoPoiService travelInfoPoiService;

    // 定义Bing地图API的密钥，用于身份验证
    public static final String bing_key = "AgnUyXnyNSQjJxHYsvyrzo9CR7e1qeTX_7wXbpmO6y-o1RwwnU0-FMCc2lyh-1jU";
    // 定义Bing地图路线服务的基本URL，用于查询驾驶路线
    public static final String bing_map_route = "http://dev.virtualearth.net/REST/V1/Routes/Driving";

    /**
     * 根据指定的起始地点、结束地点和服务时间，获取地图路线信息
     *
     * @param maxSolns 最大解决方案数量，用于指定返回的最大路线数量
     * @param startLocation 起始地点，格式为纬度,经度
     * @param endLocation 结束地点，格式为纬度,经度
     * @param serviceTime 服务时间，即路线计算的服务时间
     * @return 返回地图路线的JSON字符串
     * @throws IOException 如果在发送HTTP请求或处理响应时发生错误
     */
    public static String mapRoute(Integer maxSolns, String startLocation, String endLocation, String serviceTime) throws IOException {
        // 构建完整的Bing地图路线服务URL
        String url = getBingRoteUrl(maxSolns, startLocation, endLocation, serviceTime);
        // 发送HTTP GET请求并获取响应实体的内容
        return EntityUtils.toString(HttpClients.createDefault().execute(new HttpGet(url)).getEntity(), "utf-8");
    }

    /**
     * 构建Bing地图路线查询的完整URL
     *
     * @param maxSolns 最大解决方案数量
     * @param startLocation 起始地点，格式为纬度,经度
     * @param endLocation 结束地点，格式为纬度,经度
     * @param dateStr 日期字符串，表示路线计算的时间
     * @return 返回构建好的Bing地图路线查询URL
     */
    private static String getBingRoteUrl(Integer maxSolns, String startLocation, String endLocation, String dateStr) {
        // 拼接URL，包含基本URL、查询参数和API密钥等信息
        return bing_map_route + "?distanceUnit=Kilometer&wp.0=" + startLocation + "&wp.1=" + endLocation + "&du=km&key=" + bing_key + "&maxSolns="
                + maxSolns + "&dateTime=" + dateStr;
    }

    public static Date parseDate(String dateString) {
        if (StringUtils.isBlank(dateString)) {
            return null;
        }
        // 从字符串中提取时间戳和时区偏移信息
        int startIndex = dateString.indexOf("(");
        int endIndex = dateString.indexOf(")");
        String timestampString = dateString.substring(startIndex + 1, endIndex);
        String[] parts = timestampString.split("-");
        long timestamp = Long.parseLong(parts[0]);
        int timeZoneOffset = Integer.parseInt(parts[1]) * 36_000; // 将时区偏移量转换为毫秒

        // 创建 SimpleDateFormat 实例并设置时区
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为 GMT

        // 使用时间戳创建 Date 对象
        Date date = new Date(timestamp + timeZoneOffset);

        return date;
    }

    /**
     * 计算地图路线信息
     *
     * @param startLocation 起始位置
     * @param endLocation   目标位置
     * @return 返回计算后的地图路线信息，包括平均行驶距离和平均行驶时间
     */
    public MapRouteInfo calculateMapRoute(String startLocation, String endLocation) throws IOException {
        // 初始化地图路线信息对象
        MapRouteInfo mapRouteInfo = new MapRouteInfo();
        // 调用地图服务获取路线信息，参数包括：3（未知）、起始位置、目标位置、3天后的日期格式化后的时间
        String mapRoute = mapRoute(3, startLocation, endLocation, DateFormatUtils.format(DateUtils.addDays(new Date(), 3), "yyyy-MM-dd HH:mm:ss"));

        // 将获取到的路线信息解析为JSON对象
        JSONObject jsonObject = JSONObject.parseObject(mapRoute);
        // 获取资源集数组
        JSONArray resourceSets = jsonObject.getJSONArray("resourceSets");

        // 如果资源集为空，则记录警告信息并返回空的路线信息
        if (resourceSets.isEmpty()) {
            log.warn("【bing地图】返回无效距离");
            return mapRouteInfo;
        }

        // 初始化总行驶距离和总行驶时间
        Double sumTravelDistance = 0d;
        Integer sumTravelDuration = 0;

        // 遍历资源集
        for (int i = 0; i < resourceSets.size(); i++) {
            // 获取当前资源集中的资源数组
            JSONArray resources = resourceSets.getJSONObject(i).getJSONArray("resources");

            // 遍历资源数组
            for (int index = 0; index < resources.size(); index++) {
                // 获取当前资源对象
                JSONObject d = resources.getJSONObject(index);
                // 获取当前资源的路线段数组
                JSONArray routeLegs = d.getJSONArray("routeLegs");

                // 累加行驶距离和行驶时间，如果为null则默认为0
                sumTravelDistance += ObjectUtils.defaultIfNull(d.getDouble("travelDistance"), 0d);
                sumTravelDuration += ObjectUtils.defaultIfNull(d.getInteger("travelDuration"), 0);

                // 解析路线段的开始时间和结束时间（此处未使用，可能是为后续处理准备）
                Date startTime = parseDate(routeLegs.getJSONObject(0).getString("startTime"));
                Date endTime = parseDate(routeLegs.getJSONObject(0).getString("endTime"));
            }
        }

        // 计算平均行驶距离和平均行驶时间，并设置到路线信息对象中
        mapRouteInfo.setTravelDistance(sumTravelDistance / resourceSets.size());
        mapRouteInfo.setTravelDuration(sumTravelDuration / resourceSets.size());
        // 返回计算后的路线信息
        return mapRouteInfo;
    }


    /**
     * 添加并保存旅行信息
     *
     * @param travelInfoReq 旅行信息请求对象，包含旅行基本信息及POI信息
     * @return ResultDTO 返回操作结果及可能的错误信息
     */
    public ResultDTO addAndSaveTravelInfo(TravelInfoReq travelInfoReq) {

        // 初始化结果对象为成功状态
        ResultDTO resultDTO = ResultDTO.success();
        try {
            // 从请求对象中提取基本信息
            String travelNo = travelInfoReq.getTravelNo();
            String travelName = travelInfoReq.getTravelName();
            Date travelStartTime = travelInfoReq.getTravelStartTime();
            Date travelEndTime = travelInfoReq.getTravelEndTime();
            Integer totalDay = travelInfoReq.getTotalDay();
            Integer startCityId = travelInfoReq.getStartCityId();
            String startCityName = travelInfoReq.getStartCityName();
            Integer endCityId = travelInfoReq.getEndCityId();
            String endCityName = travelInfoReq.getEndCityName();

            // 根据travelNo查询现有旅行信息
            TravelInfo existingTravelInfo = null;
            if (travelNo != null && ! travelNo.isEmpty()) {
                existingTravelInfo = this.travelInfoService.getByTravelNo(travelNo);
            }

            // 处理新创建或更新的情况
            TravelInfo newTravelInfo;
            if (existingTravelInfo == null) {
                // 创建新的旅行信息对象并设置属性
                newTravelInfo = new TravelInfo();
                newTravelInfo.setTravelNo(travelNo);
            } else {
                // 验证travelNo对应的用户是否一致
                if (! existingTravelInfo.getUserId().equals(travelInfoReq.getUserId())) {
                    throw new IllegalArgumentException("行程对应的用户不一致");
                }
                newTravelInfo = existingTravelInfo;
                // 更新属性
                newTravelInfo.setTravelName(travelName);
                newTravelInfo.setTravelStartTime(travelStartTime);
                newTravelInfo.setTravelEndTime(travelEndTime);
                newTravelInfo.setTotalDay(totalDay);
                newTravelInfo.setStartCityId(startCityId);
                newTravelInfo.setStartCityName(startCityName);
                newTravelInfo.setEndCityId(endCityId);
                newTravelInfo.setEndCityName(endCityName);
            }

            // 保存或更新新的旅行信息
            this.travelInfoService.saveOrUpdate(newTravelInfo);

            // 从请求对象中提取关联的poi信息
            List<TravelInfoPoiReq> travelInfoPoiReqs = travelInfoReq.getTravelInfoPoiList();

            // 创建并保存旅行信息和poi的关联数据
            List<TravelInfoPoi> travelInfoPois = travelInfoPoiReqs.stream().map(travelInfoPoiReq -> {
                TravelInfoPoi travelInfoPoi = new TravelInfoPoi();
                travelInfoPoi.setTravelNo(travelNo);
                travelInfoPoi.setTravelRemark(travelInfoPoiReq.getTravelRemark());
                travelInfoPoi.setTravelPoiId(travelInfoPoiReq.getTravelPoiId());
                travelInfoPoi.setTravelStartTime(travelInfoPoiReq.getTravelStartTime());
                travelInfoPoi.setTravelEndTime(travelInfoPoiReq.getTravelEndTime());
                travelInfoPoi.setTravelOrder(travelInfoPoiReq.getTravelOrder());
                return travelInfoPoi;
            }).collect(Collectors.toList());

            // 保存旅行信息和poi的关联数据
            this.travelInfoPoiService.saveOrUpdateBatch(travelInfoPois);
        } catch (Exception e) {
            // 记录错误日志
            log.error("添加并保存行程信息出错", e);
            // 修改结果对象为错误状态，并返回错误信息
            resultDTO = ResultDTO.error(e.getMessage());

        }
        // 返回结果对象
        return resultDTO;
    }


    /**
     * 根据旅行信息查询条件获取旅行详情和相关的poi信息
     *
     * @param infoReq 旅行信息查询请求对象，包含查询关键字、限制和偏移量等参数
     * @return 包含旅行详情和poi信息的分页数据对象
     */
    public PageData<TravelInfoRsp> getTravelDetail(QueryTravelInfoReq infoReq) {
        // 初始化分页数据对象
        PageData<TravelInfoRsp> pageData = new PageData<>();

        // 从查询请求中提取参数
        String keyword = infoReq.getKeyword();
        int limit = infoReq.getLimit();
        int offset = infoReq.getOffset();

        try {
            // 查询符合条件的旅行信息数量  或 es搜索结果数量
            int count = this.travelInfoService.queryByKeywordCount(keyword);

            // 如果没有找到任何记录，则返回空的分页数据
            if (count == 0) {
                return pageData;
            }

            // 根据关键字查询旅行信息 或 es搜索结果
            List<TravelInfo> travelInfos = this.travelInfoService.queryByKeyword(keyword, offset, limit);

            // 提取旅行信息中的旅行编号
            Set<String> travelNos = travelInfos.stream().map(TravelInfo::getTravelNo).collect(Collectors.toSet());

            // 根据旅行编号查询旅行信息和poi的关联数据
            List<TravelInfoPoi> travelInfoPoiList = this.travelInfoPoiService.queryByTravelNos(travelNos);

            // 将poi数据按照旅行编号分组
            Map<String, List<TravelInfoPoi>> poiGroupByTravelNo = travelInfoPoiList.stream()
                    .collect(Collectors.groupingBy(TravelInfoPoi::getTravelNo));

            // 提取poi的ID集合
            Set<Long> poiIds = travelInfoPoiList.stream().map(TravelInfoPoi::getTravelPoiId).collect(Collectors.toSet());

            // 根据ID列表查询poi详细信息
            List<Poi> pois = this.poiService.queryByIds(poiIds);

            // 将poi数据按照ID分组，以便快速查找
            Map<Long, Poi> poiMap = pois.stream().collect(Collectors.toMap(Poi::getId, poi -> poi));

            // 遍历旅行信息，组装响应数据
            for (TravelInfo travelInfo : travelInfos) {
                // 初始化旅行信息响应对象，并设置基本属性
                TravelInfoRsp travelInfoRsp = new TravelInfoRsp();
                travelInfoRsp.setId(travelInfo.getId());
                travelInfoRsp.setUserId(travelInfo.getUserId());
                travelInfoRsp.setTravelNo(travelInfo.getTravelNo());
                travelInfoRsp.setTravelName(travelInfo.getTravelName());
                travelInfoRsp.setTravelStartTime(travelInfo.getTravelStartTime());
                travelInfoRsp.setTravelEndTime(travelInfo.getTravelEndTime());
                travelInfoRsp.setTotalDay(travelInfo.getTotalDay());
                travelInfoRsp.setStartCityId(travelInfo.getStartCityId());
                travelInfoRsp.setStartCityName(travelInfo.getStartCityName());
                travelInfoRsp.setEndCityId(travelInfo.getEndCityId());
                travelInfoRsp.setEndCityName(travelInfo.getEndCityName());
                travelInfoRsp.setAuditStatus(travelInfo.getAuditStatus());
                travelInfoRsp.setStatus(travelInfo.getStatus());

                // 根据旅行编号获取相关的poi信息
                List<TravelInfoPoi> poisForTravel = poiGroupByTravelNo.getOrDefault(travelInfo.getTravelNo(), Collections.emptyList());

                // 初始化poi名称列表
                List<String> poiNames = new ArrayList<>(poisForTravel.size());
                // 初始化旅行信息和poi关联的响应列表
                List<TravelInfoPoiRsp> infoPoiRsps = poisForTravel.stream().map(poit -> {
                    // 初始化旅行信息和poi关联的响应对象，并设置基本属性
                    TravelInfoPoiRsp travelInfoPoiRsp = new TravelInfoPoiRsp();
                    travelInfoPoiRsp.setId(poit.getId());
                    travelInfoPoiRsp.setTravelNo(poit.getTravelNo());
                    travelInfoPoiRsp.setTravelRemark(poit.getTravelRemark());
                    travelInfoPoiRsp.setTravelPoiId(poit.getTravelPoiId());
                    travelInfoPoiRsp.setTravelStartTime(poit.getTravelStartTime());
                    travelInfoPoiRsp.setTravelEndTime(poit.getTravelEndTime());
                    travelInfoPoiRsp.setTravelOrder(poit.getTravelOrder());

                    // 根据poiID获取poi详细信息
                    Poi poi = poiMap.get(poit.getTravelPoiId());
                    // 初始化poi响应对象，并设置属性
                    PoiRsp poiRsp = new PoiRsp();
                    poiRsp.setId(poi.getId());
                    poiRsp.setPoiId(poi.getPoiId());
                    poiRsp.setName(poi.getName());
                    poiRsp.setType(poi.getType());
                    poiRsp.setCityId(poi.getCityId());
                    poiRsp.setCityName(poi.getCityName());
                    poiRsp.setAddressCn(poi.getAddressCn());
                    poiRsp.setAddressLocal(poi.getAddressLocal());
                    poiRsp.setLng(poi.getLng());
                    poiRsp.setLat(poi.getLat());
                    poiRsp.setPoiSource(poi.getPoiSource());
                    poiRsp.setPoiSourceId(poi.getPoiSourceId());
                    poiRsp.setPrefectFlag(poi.getPrefectFlag());
                    poiRsp.setLevel(poi.getLevel());
                    poiRsp.setNameAlias(poi.getNameAlias());
                    poiRsp.setDescription(poi.getDescription());
                    poiRsp.setStatus(poi.getStatus());
                    travelInfoPoiRsp.setPoiRsp(poiRsp);
                    // 将poi名称添加到列表中
                    poiNames.add(poiRsp.getName());
                    return travelInfoPoiRsp;
                }).collect(Collectors.toList());

                // 设置旅行信息响应对象中的poi相关列表和名称
                travelInfoRsp.setTravelInfoPoiList(infoPoiRsps);
                travelInfoRsp.setPoiNames(poiNames);
                // 将旅行信息响应对象添加到分页数据列表中
                pageData.getList().add(travelInfoRsp);
            }

            // 设置分页数据的总记录数
            pageData.setTotal(count);
            // 返回分页数据
            return pageData;

        } catch (Exception e) {
            // 处理异常，例如记录日志或抛出更具体的异常
            log.error("Error while getting travel details", e);
            // 抛出运行时异常，附带错误信息和原因
            throw new RuntimeException("Error while getting travel details", e);
        }
    }
}
